{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Using Layout Templates\n", "\n", "As we showed in [Layout of Jupyter widgets](Widget%20Layout.ipynb), multiple widgets can be arranged together using the flexible [GridBox](Widget%20Layout.ipynb#The-Grid-layout) specification. However, use of the specification involves some understanding of CSS properties and may impose sharp learning curve. Here, we will describe layout templates built on top of `GridBox` that simplify creation of common widget layouts." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "remove-cell" ] }, "outputs": [], "source": [ "# Imports for JupyterLite\n", "%pip install -q ipywidgets bqplot numpy" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Utils widgets\n", "from ipywidgets import Button, Layout, jslink, IntText, IntSlider\n", "\n", "def create_expanded_button(description, button_style):\n", " return Button(description=description, button_style=button_style, layout=Layout(height='auto', width='auto'))\n", "\n", "top_left_button = create_expanded_button(\"Top left\", 'info')\n", "top_right_button = create_expanded_button(\"Top right\", 'success')\n", "bottom_left_button = create_expanded_button(\"Bottom left\", 'danger')\n", "bottom_right_button = create_expanded_button(\"Bottom right\", 'warning')\n", "\n", "top_left_text = IntText(description='Top left', layout=Layout(width='auto', height='auto'))\n", "top_right_text = IntText(description='Top right', layout=Layout(width='auto', height='auto'))\n", "bottom_left_slider = IntSlider(description='Bottom left', layout=Layout(width='auto', height='auto'))\n", "bottom_right_slider = IntSlider(description='Bottom right', layout=Layout(width='auto', height='auto'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2x2 Grid" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can easily create a layout with 4 widgets arranged on 2x2 matrix using the `TwoByTwoLayout` widget: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ipywidgets import TwoByTwoLayout\n", "\n", "TwoByTwoLayout(top_left=top_left_button,\n", " top_right=top_right_button,\n", " bottom_left=bottom_left_button,\n", " bottom_right=bottom_right_button)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you don't define a widget for some of the slots, the layout will automatically re-configure itself by merging neighbouring cells" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TwoByTwoLayout(top_left=top_left_button,\n", " bottom_left=bottom_left_button,\n", " bottom_right=bottom_right_button)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can pass `merge=False` in the argument of the `TwoByTwoLayout` constructor if you don't want this behavior" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TwoByTwoLayout(top_left=top_left_button,\n", " bottom_left=bottom_left_button,\n", " bottom_right=bottom_right_button,\n", " merge=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can add a missing widget even after the layout initialization:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layout_2x2 = TwoByTwoLayout(top_left=top_left_button,\n", " bottom_left=bottom_left_button,\n", " bottom_right=bottom_right_button)\n", "layout_2x2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layout_2x2.top_right = top_right_button" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also use the linking feature of widgets to update some property of a widget based on another widget:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "app = TwoByTwoLayout(top_left=top_left_text, top_right=top_right_text,\n", " bottom_left=bottom_left_slider, bottom_right=bottom_right_slider)\n", " \n", "link_left = jslink((app.top_left, 'value'), (app.bottom_left, 'value'))\n", "link_right = jslink((app.top_right, 'value'), (app.bottom_right, 'value'))\n", "app.bottom_right.value = 30\n", "app.top_left.value = 25\n", "app" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can easily create more complex layouts with custom widgets. For example, you can use [bqplot](http://github.com/bloomberg/bqplot) Figure widget to add plots:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import bqplot as bq\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "size = 100\n", "np.random.seed(0)\n", "\n", "x_data = range(size)\n", "y_data = np.random.randn(size)\n", "y_data_2 = np.random.randn(size)\n", "y_data_3 = np.cumsum(np.random.randn(size) * 100.)\n", "\n", "x_ord = bq.OrdinalScale()\n", "y_sc = bq.LinearScale()\n", "\n", "bar = bq.Bars(x=np.arange(10), y=np.random.rand(10), scales={'x': x_ord, 'y': y_sc})\n", "ax_x = bq.Axis(scale=x_ord)\n", "ax_y = bq.Axis(scale=y_sc, tick_format='0.2f', orientation='vertical')\n", "\n", "fig = bq.Figure(marks=[bar], axes=[ax_x, ax_y], padding_x=0.025, padding_y=0.025,\n", " layout=Layout(width='auto', height='90%'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ipywidgets import FloatSlider\n", "\n", "max_slider = FloatSlider(min=0, max=10, default_value=2, description=\"Max: \",\n", " layout=Layout(width='auto', height='auto'))\n", "min_slider = FloatSlider(min=-1, max=10, description=\"Min: \",\n", " layout=Layout(width='auto', height='auto'))\n", "app = TwoByTwoLayout(top_left=min_slider,\n", " bottom_left=max_slider, \n", " bottom_right=fig,\n", " align_items=\"center\", \n", " height='700px')\n", "\n", "jslink((y_sc, 'max'), (max_slider, 'value'))\n", "jslink((y_sc, 'min'), (min_slider, 'value'))\n", "jslink((min_slider, 'max'), (max_slider, 'value'))\n", "jslink((max_slider, 'min'), (min_slider, 'value'))\n", "\n", "max_slider.value = 1.5\n", "app" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## AppLayout" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`AppLayout` is a widget layout template that allows you to create an application-like widget arrangements. It consist of a header, a footer, two sidebars and a central pane:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ipywidgets import AppLayout, Button, Layout" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "header_button = create_expanded_button('Header', 'success')\n", "left_button = create_expanded_button('Left', 'info')\n", "center_button = create_expanded_button('Center', 'warning')\n", "right_button = create_expanded_button('Right', 'info')\n", "footer_button = create_expanded_button('Footer', 'success')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "AppLayout(header=header_button,\n", " left_sidebar=left_button,\n", " center=center_button,\n", " right_sidebar=right_button,\n", " footer=footer_button)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However with the automatic merging feature, it's possible to achieve many other layouts:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "AppLayout(header=None,\n", " left_sidebar=None,\n", " center=center_button,\n", " right_sidebar=None,\n", " footer=None)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "AppLayout(header=header_button,\n", " left_sidebar=left_button,\n", " center=center_button,\n", " right_sidebar=right_button,\n", " footer=None)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "AppLayout(header=None,\n", " left_sidebar=left_button,\n", " center=center_button,\n", " right_sidebar=right_button,\n", " footer=None)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "AppLayout(header=header_button,\n", " left_sidebar=left_button,\n", " center=center_button,\n", " right_sidebar=None,\n", " footer=footer_button)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "AppLayout(header=header_button,\n", " left_sidebar=None,\n", " center=center_button,\n", " right_sidebar=right_button,\n", " footer=footer_button)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "AppLayout(header=header_button,\n", " left_sidebar=None,\n", " center=center_button,\n", " right_sidebar=None,\n", " footer=footer_button)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "AppLayout(header=header_button,\n", " left_sidebar=left_button,\n", " center=None,\n", " right_sidebar=right_button,\n", " footer=footer_button)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also modify the relative and absolute widths and heights of the panes using `pane_widths` and `pane_heights` arguments. Both accept a sequence of three elements, each of which is either an integer (equivalent to the weight given to the row/column) or a string in the format `'1fr'` (same as integer) or `'100px'` (absolute size)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "AppLayout(header=header_button,\n", " left_sidebar=left_button,\n", " center=center_button,\n", " right_sidebar=right_button,\n", " footer=footer_button,\n", " pane_widths=[3, 3, 1],\n", " pane_heights=[1, 5, '60px'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Grid layout" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`GridspecLayout` is a N-by-M grid layout allowing for flexible layout definitions using an API similar to matplotlib's [GridSpec](https://matplotlib.org/tutorials/intermediate/gridspec.html#sphx-glr-tutorials-intermediate-gridspec-py).\n", "\n", "You can use `GridspecLayout` to define a simple regularly-spaced grid. For example, to create a 4x3 layout:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ipywidgets import GridspecLayout\n", "\n", "grid = GridspecLayout(4, 3)\n", "\n", "for i in range(4):\n", " for j in range(3):\n", " grid[i, j] = create_expanded_button('Button {} - {}'.format(i, j), 'warning')\n", "grid" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To make a widget span several columns and/or rows, you can use slice notation:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "grid = GridspecLayout(4, 3, height='300px')\n", "grid[:3, 1:] = create_expanded_button('One', 'success')\n", "grid[:, 0] = create_expanded_button('Two', 'info')\n", "grid[3, 1] = create_expanded_button('Three', 'warning')\n", "grid[3, 2] = create_expanded_button('Four', 'danger')\n", "grid" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can still change properties of the widgets stored in the grid, using the same indexing notation.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "grid = GridspecLayout(4, 3, height='300px')\n", "grid[:3, 1:] = create_expanded_button('One', 'success')\n", "grid[:, 0] = create_expanded_button('Two', 'info')\n", "grid[3, 1] = create_expanded_button('Three', 'warning')\n", "grid[3, 2] = create_expanded_button('Four', 'danger')\n", "\n", "grid" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "grid[0, 0].description = \"I am the blue one\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "**Note**: It's enough to pass an index of one of the grid cells occupied by the widget of interest. Slices are not supported in this context." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If there is already a widget that conflicts with the position of the widget being added, it will be removed from the grid:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "grid = GridspecLayout(4, 3, height='300px')\n", "grid[:3, 1:] = create_expanded_button('One', 'info')\n", "grid[:, 0] = create_expanded_button('Two', 'info')\n", "grid[3, 1] = create_expanded_button('Three', 'info')\n", "grid[3, 2] = create_expanded_button('Four', 'info')\n", "\n", "grid" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "grid[3, 1] = create_expanded_button('New button!!', 'danger')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Note**: Slices are supported in this context." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "grid[:3, 1:] = create_expanded_button('I am new too!!!!!', 'warning')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating scatter plots using GridspecLayout" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In these examples, we will demonstrate how to use `GridspecLayout` and `bqplot` widget to create a multipanel scatter plot. To run this example you will need to install the [bqplot](https://bqplot.readthedocs.io/en/latest/) package." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For example, you can use the following snippet to obtain a scatter plot across multiple dimensions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import bqplot as bq\n", "import numpy as np\n", "from ipywidgets import GridspecLayout, Button, Layout\n", "\n", "n_features = 5\n", "data = np.random.randn(100, n_features)\n", "data[:50, 2] += 4 * data[:50, 0] **2\n", "data[50:, :] += 4\n", "\n", "A = np.random.randn(n_features, n_features)/5\n", "\n", "data = np.dot(data,A)\n", "\n", "scales_x = [bq.LinearScale() for i in range(n_features)]\n", "scales_y = [bq.LinearScale() for i in range(n_features)]\n", "\n", "gs = GridspecLayout(n_features, n_features)\n", "for i in range(n_features):\n", " for j in range(n_features):\n", " \n", " if i != j:\n", " sc_x = scales_x[j]\n", " sc_y = scales_y[i]\n", "\n", " scatt = bq.Scatter(x=data[:, j], y=data[:, i], scales={'x': sc_x, 'y': sc_y}, default_size=1)\n", "\n", " gs[i, j] = bq.Figure(marks=[scatt], layout=Layout(width='auto', height='auto'),\n", " fig_margin=dict(top=0, bottom=0, left=0, right=0))\n", " else:\n", " sc_x = scales_x[j]\n", " sc_y = bq.LinearScale()\n", " \n", " hist = bq.Hist(sample=data[:,i], scales={'sample': sc_x, 'count': sc_y})\n", " \n", " gs[i, j] = bq.Figure(marks=[hist], layout=Layout(width='auto', height='auto'),\n", " fig_margin=dict(top=0, bottom=0, left=0, right=0))\n", "gs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Style attributes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can specify extra style properties to modify the layout. For example, you can change the size of the whole layout using the `height` and `width` arguments." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "AppLayout(header=None,\n", " left_sidebar=left_button,\n", " center=center_button,\n", " right_sidebar=right_button,\n", " footer=None,\n", " height=\"200px\", width=\"50%\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The gap between the panes can be increase or decreased with `grid_gap` argument:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "AppLayout(header=None,\n", " left_sidebar=left_button,\n", " center=center_button,\n", " right_sidebar=right_button,\n", " footer=None,\n", " height=\"200px\", width=\"50%\",\n", " grid_gap=\"10px\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Additionally, you can control the alignment of widgets within the layout using `justify_content` and `align_items` attributes:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ipywidgets import Text, HTML\n", "TwoByTwoLayout(top_left=top_left_button, top_right=top_right_button,\n", " bottom_right=bottom_right_button,\n", " justify_items='center',\n", " width=\"50%\",\n", " align_items='center')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For other alignment options it's possible to use common names (`top` and `bottom`) or their CSS equivalents (`flex-start` and `flex-end`):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TwoByTwoLayout(top_left=top_left_button, top_right=top_right_button,\n", " bottom_right=bottom_right_button,\n", " justify_items='center',\n", " width=\"50%\",\n", " align_items='top')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example\n", "\n", "In this [notebook](Layout%20Example.ipynb) you will find a full example using `AppLayout`.\n", "\n", "![](images/applayout-weather.png)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.0" } }, "nbformat": 4, "nbformat_minor": 4 }